﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace DiskInspector.Model.FileRecordStructure
{
    public static class FileRecordParser
    {
        public static FileRecord Parse(byte[] data)
        {
            var fileRecord = new FileRecord
            {
                FileRecordHead = ParseFileRecordHead(data)
            };

            ReplaceUpdateSequenceArray(data, fileRecord.FileRecordHead.UpdateSequenceArray);
            fileRecord.Attributes = ParseAttributes(data, fileRecord.FileRecordHead.OffsetToTheFirstAttribute).ToArray();
            return fileRecord;
        }

        private static void ReplaceUpdateSequenceArray(byte[] data, byte[] updateSequenceArray)
        {
            data[510] = updateSequenceArray[0];
            data[511] = updateSequenceArray[1];
            data[1022] = updateSequenceArray[2];
            data[1023] = updateSequenceArray[3];
        }

        private static FileRecordHead ParseFileRecordHead(byte[] data)
        {
            return DataParser.Parse<FileRecordHead>(data);
        }

        private static IEnumerable<Attribute> ParseAttributes(byte[] data, int offset)
        {
            var buffer = data.Skip(offset).ToArray();
            offset = 0;

            while (HasNextAttribute(buffer, offset))
            {
                var attribute = ParseAttribute(buffer, offset);
                offset += attribute.AttributeHead.AttributeLength;
                yield return attribute;
            }
        }

        private static Attribute ParseAttribute(byte[] data, int offset)
        {
            var buffer = data.Skip(offset).ToArray();
            var attributeHead = ParseAttributeHead(buffer);
            var attributeContent = ParseAttributeContent(
                buffer,
                attributeHead.AttributeContentOffset,
                attributeHead.AttributeLength - attributeHead.AttributeContentOffset,
                (AttributeType)BitConverter.ToInt32(attributeHead.AttributeType, 0),
                attributeHead.NonResidentFlag == 0);

            return new Attribute
            {
                AttributeHead = attributeHead,
                AttributeContent = attributeContent
            };
        }

        private static AttributeHead ParseAttributeHead(byte[] data)
        {
            AttributeHead attributeHead;
            var initialAttributeHead = DataParser.Parse<AttributeHead>(data);

            if (initialAttributeHead.NonResidentFlag == 0)
            {
                attributeHead = DataParser.Parse<ResidentAttributeHead>(data);
            }
            else
            {
                attributeHead = DataParser.Parse<NonResidentAttributeHead>(data);
            }

            Debug.Assert(
                initialAttributeHead.AttributeType.SequenceEqual(attributeHead.AttributeType),
                "Attribute head resolving failed.");

            attributeHead.AttributeName = data.Skip(attributeHead.AttributeNameOffset).Take(attributeHead.AttributeNameLength * 2).ToArray();
            return attributeHead;
        }

        private static AttributeContent ParseAttributeContent(byte[] data, int offset, int attributeContentLength, AttributeType attributeType, bool isResident)
        {
            if (attributeContentLength == 0) return null;

            var buffer = data.Skip(offset).ToArray();

            Debug.Assert(buffer.Length >= attributeContentLength, "Invalid data length.");

            switch (attributeType)
            {
                case AttributeType.StandardInformation:
                    return DataParser.Parse<StandardInformationAttributeContent>(buffer);

                case AttributeType.AttributeList:
                    var attributeListAttributeContent = DataParser.Parse<AttributeListAttributeContent>(buffer);
                    if (attributeListAttributeContent.AttributeNameLength > 0)
                    {
                        attributeListAttributeContent.FileName =
                            Encoding.Unicode.GetString(
                                buffer.Skip(attributeListAttributeContent.AttributeNameOffset)
                                      .Take(attributeListAttributeContent.AttributeNameLength * 2)
                                      .ToArray());
                    }

                    return attributeListAttributeContent;

                case AttributeType.FileName:
                    var fileNameAttributeContent = DataParser.Parse<FileNameAttributeContent>(buffer);
                    fileNameAttributeContent.FileName =
                        Encoding.Unicode.GetString(
                            buffer.Skip(FileNameAttributeContent.Length)
                                  .Take(fileNameAttributeContent.FileNameLength * 2)
                                  .ToArray());

                    return fileNameAttributeContent;

                case AttributeType.Data:
                    if (!isResident)
                    {
                        var dataAttributeContent = new NonResidentDataAttributeContent(); 
                        var runList = new List<DataAttributeContent.DataRun>();
                        var dataRunOffset = 0;

                        do
                        {
                            if (buffer[0] == 0) break;

                            var dataRun = DataParser.Parse<DataAttributeContent.DataRun>(buffer, dataRunOffset);
                            dataRun.ClusterCount = ConvertToNumeric(
                                buffer,
                                dataRunOffset + 1,
                                dataRun.ClusterCountSize);
                            dataRun.ClusterOffset = ConvertToNumeric(
                                buffer,
                                dataRunOffset + 1 + dataRun.ClusterCountSize,
                                dataRun.ClusterOffsetSize) + runList.Sum(dr => dr.ClusterOffset);
                            runList.Add(dataRun);

                            dataRunOffset += 1 + dataRun.ClusterCountSize + dataRun.ClusterOffsetSize;
                        }
                        while (dataRunOffset < attributeContentLength && buffer[dataRunOffset] != 0);

                        dataAttributeContent.RunList = runList.ToArray();
                        return dataAttributeContent;
                    }
                    else
                    {
                        var dataAttributeContent = new ResidentDataAttributeContent();
                        dataAttributeContent.FileContent = buffer.Take(attributeContentLength).ToArray();
                        return dataAttributeContent;
                    }
            }

            return null;
        }

        private static int ConvertToNumeric(byte[] buffer, int offset, byte length)
        {
            const byte SizeOfIntType = 4;

            Debug.Assert(length <= SizeOfIntType, "The length of data is overflow.");

            int result = 0;
            var data = buffer.Skip(offset).ToArray();

            for (int i = 0; i < length && i <= SizeOfIntType; i++)
                result += data[i] << (i * 8);

            //Debug.Assert(result >= 0, "The number returned should be equal or greater than zero.");

            return result;
        }

        private static bool HasNextAttribute(byte[] data, int offset)
        {
            var buffer = data.Skip(offset).ToArray();
            return !(buffer[0] == 0xFF && buffer[1] == 0xFF && buffer[2] == 0xFF && buffer[3] == 0xFF);
        }
    }
}
